namespace SimpleWebServices.Security.Cryptography
{
    using System;
    using System.Diagnostics.CodeAnalysis;
    using System.IO;
    using System.Net;
    using System.Runtime.Serialization;
    using System.Security.Cryptography;
    using System.Security.Permissions;
    using System.Text;

    [Serializable]
    public sealed class MD5Hash : IComparable, ISerializable
    {
        private string _value;

        public MD5Hash()
            : this(string.Empty)
        {
        }

        public MD5Hash(string value)
        {
            this._value = value;
        }

        private MD5Hash(SerializationInfo info, StreamingContext context)
        {
            this._value = info.GetString("_value");
        }

        public static implicit operator string(MD5Hash value)
        {
            return object.ReferenceEquals(null, value) ? null as string : value.ToString();
        }

        public static implicit operator MD5Hash(string value)
        {
            return object.ReferenceEquals(null, value) ? null as MD5Hash : new MD5Hash(value);
        }

        public static bool operator ==(MD5Hash operand1, MD5Hash operand2)
        {
            return 0 == MD5Hash.Compare(operand1, operand2);
        }

        public static bool operator !=(MD5Hash operand1, MD5Hash operand2)
        {
            return 0 != MD5Hash.Compare(operand1, operand2);
        }

        public static bool operator <(MD5Hash operand1, MD5Hash operand2)
        {
            return MD5Hash.Compare(operand1, operand2) < 0;
        }

        public static bool operator >(MD5Hash operand1, MD5Hash operand2)
        {
            return MD5Hash.Compare(operand1, operand2) > 0;
        }

        public static int Compare(MD5Hash comparand1, MD5Hash comparand2)
        {
            return object.ReferenceEquals(comparand1, comparand2)
                ? 0
                : String.Compare(
                    object.ReferenceEquals(null, comparand1) ? null as string : comparand1.ToString(),
                    object.ReferenceEquals(null, comparand2) ? null as string : comparand2.ToString(),
                    StringComparison.OrdinalIgnoreCase);
        }

        [SuppressMessage("Microsoft.Design", "CA1057:StringUriOverloadsCallSystemUriOverloads", Justification = "This is not a string URI.")]
        public static MD5Hash Compute(string content)
        {
            if (null == content)
            {
                throw new ArgumentNullException("content");
            }

            return Compute(content, UTF8Encoding.Default);
        }

        public static MD5Hash Compute(string content, Encoding encoding)
        {
            if (null == content)
            {
                throw new ArgumentNullException("content");
            }

            if (null == encoding)
            {
                throw new ArgumentNullException("encoding");
            }

            return Compute(encoding.GetBytes(content));
        }

        public static MD5Hash Compute(FileSystemInfo file)
        {
            if (null == file)
            {
                throw new ArgumentNullException("file");
            }

            MD5Hash value = null;

            using (Stream stream = File.Open(file.FullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
            {
                value = Compute(stream);
            }

            return value;
        }

        public static MD5Hash Compute(Uri resource)
        {
            if (null == resource)
            {
                throw new ArgumentNullException("resource");
            }

            MD5Hash value = null;

            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(resource);

            using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
            {
                value = Compute(response.GetResponseStream());
            }

            return value;
        }

        public static MD5Hash Compute(Stream content)
        {
            if (null == content)
            {
                throw new ArgumentNullException("content");
            }

            MD5Hash value = null;

            using (StreamReader reader = new StreamReader(content))
            {
                using (MD5 md5 = new MD5CryptoServiceProvider())
                {
                    byte[] hash = md5.ComputeHash(content);
                    value = Convert.ToBase64String(hash);
                }
            }

            return value;
        }

        public static MD5Hash Compute(byte[] content)
        {
            if (null == content)
            {
                throw new ArgumentNullException("content");
            }

            MD5Hash value = null;

            using (MD5 md5 = new MD5CryptoServiceProvider())
            {
                byte[] hash = md5.ComputeHash(content);
                value = Convert.ToBase64String(hash);
            }

            return value;
        }

        public override bool Equals(object obj)
        {
            bool result = false;

            if (!object.ReferenceEquals(null, obj))
            {
                if (object.ReferenceEquals(this, obj))
                {
                    result = true;
                }
                else
                {
                    MD5Hash cast = obj as MD5Hash;

                    if (!object.ReferenceEquals(null, cast))
                    {
                        result = 0 == MD5Hash.Compare(this, cast);
                    }
                }
            }

            return result;
        }

        public override int GetHashCode()
        {
            return this.ToString().GetHashCode();
        }

        public override string ToString()
        {
            return this._value;
        }

        public int CompareTo(object obj)
        {
            int comparison = 1;

            if (!object.ReferenceEquals(null, obj))
            {
                MD5Hash value = obj as MD5Hash;

                if (object.ReferenceEquals(null, value))
                {
                    throw new ArgumentException("Object must be of type \"MD5Hash\".", "obj");
                }

                comparison = MD5Hash.Compare(this, value);
            }

            return comparison;
        }

        [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
        {
            if (null == info)
            {
                throw new ArgumentNullException("info");
            }

            info.AddValue("_value", this._value);
        }
    }
}